#!/usr/bin/env python
# -*- encoding: utf-8 -*-
'''
@File    :   sensor_demo.py
@Time    :   2024/05/04 09:54:12
@Author  :   Jim 
@Version :   1.0.1
@Contact :   jim@yi-win.com
@License :   (C)Copyright 2021-2025, 奕枫仪器
@Desc    :   None
'''

# here put the import lib

from tkinter import *
from tkinter import ttk
import tkinter.messagebox
import threading
import serial
import serial.tools.list_ports
import  sys
import time
import logging
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass,field
from typing import List
from isensor import CMD_REPLY, isensor, sensor_hex
from isensor import sensor_ascii,sensor_enviroFlu

logger = logging.getLogger(__name__)
logger.setLevel(level=logging.INFO)

formatter = logging.Formatter('%(asctime)s - %(name)s [%(lineno)s] - %(levelname)s - %(message)s')

console_handler = logging.StreamHandler()  # 流句柄
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

class sensor_demo(object):
    def __init__(self,):
        self.data_type = 1
        self.text_rcv = ""
        self.text_snd = ""
        self.var_hex_ascii = ""

        self.cmd_reply:CMD_REPLY      = CMD_REPLY(1, ["0103000000044409"], ["0103000000044409"], 20)
        
        self.label98:ttk.Label  = None 
        self.label_memo:ttk.Label = None 
        self.init_window:Tk = Tk()
        self.hint_label:ttk.Label =  None
        self.serial_com = []
        self.combo_port:ttk.Combobox = None

        self.ser  = None  
        self.port = None      
        self.baudrate = None      
        self.databit = None      
        self.stopbit = None      
        self.parity = None      
        
        self.bufmachine = buf_machine()
        self.bufmachine.clean()

        self.uart                   =  Uart() 
        self.bufmachine:buf_machine = buf_machine()
        self.uart.uart_set_buf_machine(self.bufmachine)
        self.bufmachine.clean()    

        self.sensors = []
        # self.curr_sensor:isensor = None
        # self.curr_sensor_handle:isensor = None
                
        self.sensor = sensor_hex("sensorHex")
        self.uart.uart_set_sensor(self.sensor)
        self.uart.bufmachine.set_validate(self.uart.sensor.validate)
        self.uart.sensor.handle()        
        self.uart.bufmachine.set_callback(self.sensor_callback)
        
        self.__time = 0
        
        pass
    
    def gui(self,):
        # init_window = Tk()
        self.init_window.title(u'传感器模拟器--上海奕枫仪器设备有限公司 模拟阻塞型RTU ASCII传感器用于测试')
        self.init_window.geometry("800x600")
    
        frame_root = Frame(self.init_window)
        frame_left = Frame(frame_root)
        frame_right = Frame(frame_root)
    
        pw1 = PanedWindow(frame_left,orient=VERTICAL)
        pw2 = PanedWindow(frame_left,orient=VERTICAL)
        pw3 = PanedWindow(frame_left,orient=VERTICAL)
    
        frame1 = LabelFrame(pw1,text=u"串口设置")
        frame2 = Frame(frame_left)
        frame3 = LabelFrame(pw2,text=u"接收设置")
        # frame4 = LabelFrame(pw3,text="发送设置")
    
        pw1.add(frame1)
        pw1.pack(side=TOP)
        frame2.pack(side=TOP)
        pw2.add(frame3)
        pw2.pack(side=TOP)
        # pw3.add(frame4)
        # pw3.pack()

        pw4 = PanedWindow(frame_right,orient=VERTICAL)   

        self.frame_sensor = LabelFrame(pw4,text=u"奕枫传感器")
        frame5 = LabelFrame(pw4,text=u"接受区校验字段设置")
        frame6 = LabelFrame(pw4,text=u"发送内容")
        frame7 = LabelFrame(pw4,text=u"接收到的数据")
        frame8 = LabelFrame(pw4,text=u"使用说明")

        pw4.add(self.frame_sensor)
        pw4.pack( side = TOP)
        pw4.add(frame5)
        pw4.pack( side = TOP)
        pw4.add(frame6)
        pw4.pack( side = TOP)
        pw4.add(frame7)
        pw4.pack( side =TOP)
        pw4.add(frame8)
        pw4.pack( side =TOP)


        self.set_frame_sensors()
 
        self.label_rcv = Label(frame5,text= self.cmd_reply.cmd[0],height=6,bg='#87CEEB',width=100,anchor="nw")
        self.label_rcv.grid(column=0,row=0)


        self.label_snd = Label(frame6,text= self.cmd_reply.reply[0],height=6,bg='#87CEEB',width=100,anchor="nw")
        self.label_snd.grid(column=0,row=0)

        self.text_rcv = Text(frame7,width=100,height=10 )
        self.text_rcv.grid(column=0,row=0)

        label_memo = Label(frame8,text=u"使用说明",height=4)
        label_memo.grid(column=0,row=0) 
    
        label1 = Label(frame1,text=u"串口号",height=2)
        label1.grid(column=0,row=0)
        label2 = Label(frame1,text=u"波特率",height=2)
        label2.grid(column=0,row=1)
        label3 = Label(frame1,text=u"数据位",height=2)
        label3.grid(column=0,row=2)
        label4 = Label(frame1,text=u"校验位",height=2)
        label4.grid(column=0,row=3)
        label5 = Label(frame1,text=u"停止位",height=2)
        label5.grid(column=0,row=4)
        # label6 = Label(frame1,text=u"超时ms",height=2)
        # label6.grid(column=0,row=5)
            
        varPort = StringVar()
        self.combo_port = ttk.Combobox(frame1,textvariable = varPort, width=8,height=5,justify=CENTER)
        self.update_com_port()
        self.combo_port.bind( "<<ComboboxSelected>>",lambda event:self.combo1_handler(var=varPort.get()) )
        self.combo_port.current(0)
        self.combo_port.grid( column=1, row=0 )
        
        varBitrate  = StringVar()
        combo2 = ttk.Combobox(frame1, textvariable = varBitrate, width=8,height=8,justify=CENTER)
        combo2['values']=("4800","9600","14400","19200","38400","56000","57600","115200")
        combo2.bind("<<ComboboxSelected>>",lambda event:self.combo2_handler(var=varBitrate.get()))
        combo2.current(1)
        combo2.grid(column=1,row=1)

        varDataBits  = StringVar()        
        combo3 = ttk.Combobox(frame1, textvariable = varDataBits, width=8,height=2,justify=CENTER)
        combo3['values']=("8Bit")
        combo3.current(0)
        combo3.grid(column=1,row=2)

        varParity = StringVar()            
        combo4 = ttk.Combobox(frame1, textvariable = varParity,width=8,height=5,justify=CENTER)
        combo4['values']=("NONE","ODD","EVEN","MARK","SPACE")
        combo4.current(0)
        combo4.grid(column=1,row=3)
        
        varStopBits = StringVar()            
        combo5 = ttk.Combobox(frame1, textvariable = varStopBits,width=8,height=3,justify=CENTER)
        combo5['values'] = ("1 bit", "1.5 bit", "2 bit" )
        combo5.current(0)
        combo5.grid(column=1,row=4)

        # varTimeout = StringVar()            
        # combo6 = ttk.Combobox(frame1, textvariable = varTimeout,width=12,height=3,justify=CENTER)
        # combo6['values'] = ("100", "200", "400", "500", "1000", "2000", "5000")
        # combo6.current(0)
        # combo6.grid(column=1,row=5)
    
        var1 = StringVar()
        var1.set(u"打开串口监听")
        button1 = Button(frame2,textvariable=var1,width=18,height=1)
        button1.bind("<Button-1>",lambda event:self.sensor_port_open( var=var1,port_=self.combo_port.get(), baudrate_=combo2.get())  )
        button1.grid(column=0,row=0)
    
        """
        接收设置
        """
        # global var2 
        self.var_hex_ascii = IntVar()
        self.var_hex_ascii.set(1)
        radio1 = ttk.Radiobutton(frame3, variable=self.var_hex_ascii, text='Hex',value=1, command = self.radiao_handle1 )
        radio2 = ttk.Radiobutton(frame3, variable=self.var_hex_ascii, text='ASCII',value=2, command = self.radiao_handle2 )
        radio1.grid(column=0,row=0)
        radio2.grid(column=0,row=1)

        frame_left.pack(side=LEFT)
        frame_right.pack(side=RIGHT)
        frame_root.pack()
        self.init_window.mainloop()
        pass
    pass

    def update_com_port(self, ):
        com_portlist = list(serial.tools.list_ports.comports())
        portcnt = 0
        portcnt = len(com_portlist)
        
        if portcnt == 0:
            self.serial_com.append("COM0")   
        else:         
            for m in range(portcnt):
                port_list_1 = list(com_portlist[m])
                self.serial_com.append(port_list_1[0])
        self.combo_port['values'] = self.serial_com

    """
    串口号改变回掉函数
    """
    def combo1_handler(self,var):
        self.port = var
        self.update_com_port()
        print(__file__, "lienno:" , sys._getframe().f_lineno, var, self.port)


    def combo2_handler(self,var):
        self.baudrate = var
        print(__file__, sys._getframe().f_lineno, var, self.baudrate)
        
    def combo3_handler(self,var):
        self.baudrate = var
        print(__file__, sys._getframe().f_lineno, var, self.baudrate)

    """
    Radiao1 ASCII回掉函数
    """   
    def radiao_handle1(self, ):
        print ( "您选择了 Hex 格式 "  )


    def radiao_handle2(self, ):
        print ( "您选择了 ASCII 格式  ") 
        
        
    def show_messagebox(self, para1, para2):
        tkinter.messagebox.showinfo(para1,para2)

    """
    串口打开关闭函数
    """
    def sensor_port_open(self, var, port_, baudrate_):    
        print(__file__,sys._getframe().f_lineno,port_,baudrate_, var.get())
        if var.get() == u"打开串口监听":
            var.set(u"关闭串口")
            self.uart.uart_open(port_,baudrate_)
            self.__time = time.time()
        else :
            var.set(u"打开串口监听")
            self.ser.close()        

    """
    串口发送函数
    """
    def usart_sent(self, seq):
        self.uart.uart_sent(seq)
    
    def register_sensor(self, sensor:isensor):
        self.sensors.append(sensor)
        pass

    def set_frame_sensors(self,  ):
        self.var_frame_sensor = IntVar()
        self.frame_sensor_radios:list[ttk.Radiobutton]  = []
        print(f"set_frame_sensors......{len(self.sensors)}")
        
        _column = 0
        _row = 0
        self.frame_sensor_radios = []
        for i  in range( len(self.sensors) ):
            snsr:isensor = self.sensors[i]
            _column = 2*i
            if (i%4 == 0):
                _row += 1
            self.frame_sensor_radios.append(ttk.Radiobutton( self.frame_sensor, variable=self.var_frame_sensor, text=snsr.sensor_name, value=i, command = self.sensor_handle  ) )
            self.frame_sensor_radios[i].grid(column=_column, row=_row)
            # self.frame_sensor_radios[i]..__getattribute__()
            i+=1

        self.var_frame_sensor.set(0)
        
    def set_frame_sensor(self, ):
        pass
    
    def sensor_handle(self  ):
        _index = self.var_frame_sensor.get()
        logger.info(f"sensor_handle=============={_index}  ")
        
        self.sensor = self.sensors[_index]
        self.uart.uart_set_sensor(self.sensor)
        self.uart.bufmachine.set_validate(self.uart.sensor.validate)
        self.uart.sensor.handle()
        # self.uart.uart_open(TEST_PORT,TEST_BAUD)
        
        #set buf_machine callback
        self.uart.bufmachine.set_callback(self.sensor_callback)
        
        self.curr_sensor:isensor = self.sensors[_index]
        self.curr_sensor.handle()
        self.cmd_reply =  self.curr_sensor.cmd_reply        
        self.data_type = self.cmd_reply.hex_ascii
        
        tmpstr = ""
        size_cmd = len(self.sensor.cmd_reply.cmd)
        # for _cmd in self.sensor.cmd_reply.cmd:
        for i in range(size_cmd):
            if i== size_cmd-1:
                tmpstr = tmpstr + self.sensor.cmd_reply.cmd[i]
            else:
                tmpstr = tmpstr + self.sensor.cmd_reply.cmd[i] +"\n"
        self.label_rcv.config( text= tmpstr)
        
    
        tmpstr = ""
        size_cmd = len(self.sensor.cmd_reply.reply)
        for i in range(size_cmd):
            if i== size_cmd-1:
                tmpstr = tmpstr + self.sensor.cmd_reply.cmd[i]
            else:
                tmpstr = tmpstr + self.sensor.cmd_reply.cmd[i] +"\n"
        self.label_snd.config( text= tmpstr)


        # 设置类型
        self.data_type =self.cmd_reply.hex_ascii
        self.var_hex_ascii.set(self.data_type)

        print(f" cmd_reply  {self.cmd_reply.cmd}   {self.cmd_reply.reply}")
        pass

    def  rcv_match (self, match_str:str ):
        flag    = 0
        place   = 0
        for i in range(0,len(self.sensor.cmd_reply.cmd)):
            if self.sensor.cmd_reply.cmd[i].startswith(match_str) and self.sensor.cmd_reply.cmd[i] == match_str:
                flag    = 2
                place   = i
                return flag,place
            if self.sensor.cmd_reply.cmd[i].startswith(match_str) and self.sensor.cmd_reply.cmd[i] != match_str:
                flag    = 1 
                place   = i
        return flag,place
        pass

    def is_timeouted (self  ):
        return 1 if (time.time()-self.__time >self.uart.sensor.timeout) else 0
        pass

    def set_time  (self  ):
        self.__time = time.time()

    def set_text_rcv(self, info ):
        self.text_rcv.insert("end", time.strftime( "[%Y-%m-%d %H:%M:%S]", time.localtime()) +  ": " +self.uart.bufmachine.res + "\n")
        
    def sensor_callback(self , data ):
        logger.info(f"call back  match.....{data}")

        if len(data) ==0 :
            return
        self.set_text_rcv(  time.strftime( "[%Y-%m-%d %H:%M:%S]", time.localtime()) +  ": " +self.uart.bufmachine.res + "\n")
        (match0 ,match1) = self.rcv_match(data)
        
        if match0 == 2:
            self.usart_sent(match1)
            self.set_time()
            self.uart.bufmachine.clean()
            return
        elif match0 == 1 and self.is_timeouted():
            self.set_time()
            self.uart.bufmachine.clean()
            pass
        elif match0 == 1 and not self.is_timeouted():
            self.set_time()
            pass
        elif match0 == 0 and not self.is_timeouted():
            self.set_time()
            self.uart.bufmachine.clean()
        else:
            pass
        pass

class Uart(object):
    def __init__(self,  ):
        self.ser  = None  
        self.port = None      
        self.baudrate = None      
        self.databit = None      
        self.stopbit = None      
        self.parity = None  
        
        self.rcv_timeout = None  
        self.rcv_ok = False  

        self.sensor:isensor         = None
        self.bufmachine:buf_machine =buf_machine()
        self.thread:threading.Thread = None

    def uart_set_buf_machine(self,machine ):
        self.bufmachine =  machine
        self.bufmachine.clean()

    def uart_set_sensor(self,sensor:isensor):
        self.sensor =  sensor

    def uart_thread_recv(self, func):
        """
        串口数据接受线程
        """
        readout = b''
        if self.ser.is_open is False:
            self.ser.open()     
            
        while(True):
            # logger.info("threading.....3")
            cnt = 0
            try:
                cnt =self.ser.in_waiting
            except Exception:
                pass
            if cnt :
                logger.info(f"threading.....{cnt}")
                readout =  self.ser.read(cnt) 
                func(readout)
            time.sleep(0.02)    

    def  uart_open(self, port_, baudrate_):
        if self.ser is not None:
            self.ser.close()
            logger.info(f"uart open fail")
        self.ser = serial.Serial(
                                port = port_,
                                baudrate=int(baudrate_),
                                parity=serial.PARITY_NONE,
                                stopbits=serial.STOPBITS_ONE,
                                bytesize=serial.EIGHTBITS )            

        if self.ser.isOpen():
            self.thread = threading.Thread( target=self.uart_thread_recv, args=(self.bufmachine.process,) )
            self.thread.setDaemon(True)
            self.thread.start()
            return True
        return False 

    def uart_sent(self, cmd_seq):
        self.rcv_ok = False
        self.bufmachine.clean()
        snd_ = b''
        if self.sensor.cmd_reply.hex_ascii == 1:
            snd_ =  bytes.fromhex(self.sensor.cmd_reply.cmd[cmd_seq])
        if self.sensor.cmd_reply.hex_ascii == 2:
            snd_  = bytes(self.sensor.cmd_reply.cmd[cmd_seq], encoding='utf8')
        if self.ser.isOpen():
            self.ser.write(snd_)

    def uart_close(self, ):
        self.ser.close()

class buf_machine(object):
    # 构造方法
    def __init__(self, ):
        self.hex_ascii = 1
        self.buf = b''
        self.res = ""
        self.rcv_ok = False
        self.validate = None
        self.callback = None
        pass

    def set_hex_ascii (self,hex_ascii):
        self.hex_ascii = hex_ascii
        pass

    def set_validate (self,validate):
        self.validate = validate
        pass

    def set_callback (self,cb):
        self.callback = cb
        pass

    def clean (self, ):
        self.buf = b''
        self.res = ""
        self.rcv_ok = False
        pass

    def get_res (self, ):
        return self.res

    def get_buf (self, ):
        return self.buf

    def process(self,_byte:bytes):
        logger.info(f'process.........rcv: {_byte}')
        self.buf += _byte
        tmp= ""
        if self.hex_ascii == 1:
            tmp = _byte.hex()
        if self.hex_ascii == 2 and  _byte != b'\r'  and _byte != b'\n' :
            tmp = _byte.decode()
        self.res += tmp
        if self.callback is not None:
            self.callback(self.res)
        return self.res
        pass

if __name__ == "__main__":
    logger.info(" ********* Begin to simulate sensor ********* ")
    sd = sensor_demo()
    
    snsrHex  = sensor_hex("sensorHex")
    snsrASCII  = sensor_ascii("sensorASCII")
    snsrEnviroflu  = sensor_enviroFlu("sensorEnviroflu")
    sd.register_sensor(snsrHex)
    sd.register_sensor(snsrASCII)
    sd.register_sensor(snsrEnviroflu)
    
    sensor_info = "Registered sesnor count: " +  str({len(sd.sensors)}) + "  "
    for i in range(len(sd.sensors)):
        sensor_info += sd.sensors[i].sensor_name + "  "
    logger.info(   sensor_info )
    sd.gui()

    # print( sd.sensors  ,  len(sd.sensors ) )

























    # def  usart_ctrl(self, var, port_, baudrate_):
    #     print(__file__,sys._getframe().f_lineno,port_,baudrate_, var.get())
    #     if var.get() == u"打开串口监听":
    #         var.set(u"关闭串口")
    #         self.ser = serial.Serial(
    #             port = port_,
    #             baudrate=int(baudrate_),
    #             parity=serial.PARITY_NONE,
    #             # timeout=100,
    #             stopbits=serial.STOPBITS_ONE,
    #             bytesize=serial.EIGHTBITS)
    #         logger.info(f"port: {self.port}, baudrate: {self.baudrate}")
    #         #ser.open()

    #         recv_data = threading.Thread(target=self.thread_recv)
    #         recv_data.setDaemon(True)
    #         recv_data.start()

    #     else :
    #         var.set(u"打开串口监听")
    #         self.ser.close()


    # def thread_recv(self,):
    #     """
    #     串口数据接受线程，需要加入超时
    #     """
    #     read = b''
    #     readout = b''
    #     alreay_received = False
    #     read2=[]
    #     _time = time.time()
    #     tmp_res = "" 

    #     if self.ser.is_open is False:
    #         self.ser.open()     
            
    #     while(True):
    #         if self.ser.in_waiting:
    #             # readout =  self.ser.read(1) 
    #             readout =  self.ser.read(self.ser.in_waiting) 
    #             tmp_res = self.bufmachine.process(readout)
    #             ( match0, match1 ) = self.rcv_match(tmp_res) 
    #             if match0 == 0:
    #                 self.hint_label.config( text= time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime())+ ' 接收数据不符合要求 ' + tmp_res)
    #                 self.bufmachine.clean( )
    #                 # self.ser.write( bytes.fromhex(self.cmd_reply.reply[match1]) ) 
    #             if match0 == 2:
    #                 _time = time.time()
    #                 self.hint_label.config( text= time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime())+ ' 收到 : ' + tmp_res)
    #                 self.usart_sent( match1 )
    #                 self.bufmachine.clean( )
    #                 alreay_received = False
    #             if match0 == 1:
    #                 _time = time.time()
    #                 alreay_received = True
    #                 continue  
    #             _time = time.time()
    #         else:
    #             if ( (time.time() - _time )> 1 )  :
    #                 if (len(tmp_res)>0):
    #                     self.hint_label.config( text= time.strftime( "%Y-%m-%d %H:%M:%S", time.localtime())+ ' 超时 : ' + tmp_res) 
    #                     tmp_res = ""
    #                 self.bufmachine.clean( )
    #                 _time = time.time()
    #                 time.sleep( 0.05)